package ports

import (
	"bytes"
	"ddd-practice/app"
	"ddd-practice/app/command"
	"ddd-practice/app/query"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"strconv"
	"strings"
	"unsafe"
)

type HttpServer struct {
	application app.Application
	server      *http.Server
}

func NewHttpServer() HttpServer {
	return HttpServer{
		application: app.NewApplication(),
	}
}

// server
func (s HttpServer) Start() {

	var urlPrefix = "/ddd/product"

	var mux = http.NewServeMux()

	mux.HandleFunc(urlPrefix+"", s.Method("GET", s.GetAllProducts))
	mux.HandleFunc(urlPrefix+"/query", s.Method("GET", s.IsProductAvailable))

	mux.HandleFunc(urlPrefix+"/put-on", s.Method("POST", s.PutOnShelves))
	mux.HandleFunc(urlPrefix+"/put-off", s.Method("PUT", s.PutOffShelves))

	s.server = &http.Server{
		Addr:    ":3000",
		Handler: mux,
	}

	log.Println("listening on :3000")

	go log.Fatalln(s.server.ListenAndServe())
}

func (s HttpServer) Stop() {
	s.server.Close()
}

// request infrastructure

type requestHandler func(w http.ResponseWriter, r *http.Request)

func (s HttpServer) Method(requestMethod string, handler requestHandler) requestHandler {
	return func(w http.ResponseWriter, r *http.Request) {
		if strings.ToUpper(r.Method) != strings.ToUpper(requestMethod) {
			s.Response(w, 403, "", "method not supported")
			return
		}
		handler(w, r)
	}
}

func (s HttpServer) Response(w http.ResponseWriter, statusCode int, data any, msg string) {

	if statusCode != 200 {
		w.WriteHeader(statusCode)
	}

	jsonData, err := json.Marshal(map[string]any{
		"data": data,
		"msg":  msg,
	})
	if err != nil {
		io.WriteString(w, err.Error())
		return
	}

	io.WriteString(w, *(*string)(unsafe.Pointer(&jsonData)))
}

// Handler
func (s HttpServer) IsProductAvailable(w http.ResponseWriter, r *http.Request) {

	value := r.URL.Query().Get("id")

	if value == "" {
		s.Response(w, 400, "", "缺失参数")
		return
	}
	productId, err := strconv.Atoi(value)

	isAvailable, err := s.application.Querys.ProductAvailabilityHandler.Handle(r.Context(), query.ProductAvailability{
		Id: productId,
	})
	if err != nil {
		s.Response(w, 500, "", err.Error())
		return
	}

	s.Response(w, 200, isAvailable, "")
}

func (s HttpServer) GetAllProducts(w http.ResponseWriter, r *http.Request) {

	list, err := s.application.Querys.AllProductsHandler.Handle(r.Context())
	if err != nil {
		s.Response(w, 500, "", err.Error())
		return
	}

	respList := make([]ProductDTO, 0, len(*list))
	dto := ProductDTO{}
	for _, v := range *list {
		dto.GenerateFromDomainObject(&v)
		respList = append(respList, dto)
	}

	s.Response(w, 200, respList, "")
}

func (s HttpServer) PutOnShelves(w http.ResponseWriter, r *http.Request) {

	var productDTO = ProductDTO{}

	buf := bytes.Buffer{}
	_, err := buf.ReadFrom(r.Body)
	if err != nil {
		s.Response(w, 500, "", err.Error())
		return
	}

	err = json.Unmarshal(buf.Bytes(), &productDTO)
	if err != nil {
		s.Response(w, 500, "", err.Error())
		return
	}

	productDO, err := productDTO.ToDomainObject()
	if err != nil {
		s.Response(w, 500, "", err.Error())
		return
	}

	err = s.application.Commands.PutOnShelvesHandler.Handle(r.Context(), productDO)
	if err != nil {
		s.Response(w, 500, "", err.Error())
		return
	}

	s.Response(w, 200, "", "product created!")

}

func (s HttpServer) PutOffShelves(w http.ResponseWriter, r *http.Request) {

	value := r.URL.Query().Get("id")

	if value == "" {
		s.Response(w, 400, "", "缺失参数")
		return
	}

	productId, err := strconv.Atoi(value)
	if err != nil {
		s.Response(w, 500, "", err.Error())
		return
	}

	err = s.application.Commands.PutOffShelvesHandler.Handle(r.Context(), command.PutOffShelves{ProductId: productId})
	if err != nil {
		s.Response(w, 500, "", err.Error())
		return
	}

	s.Response(w, 200, "", fmt.Sprintf("product [%d] off shelves!", productId))

}
